package cn.jingyuan.owl.utils.core;

import cn.jingyuan.owl.utils.core.collection.CollectionUtils;
import cn.jingyuan.owl.utils.core.convert.Convert;
import cn.jingyuan.owl.utils.core.exceptions.UtilsException;
import cn.jingyuan.owl.utils.core.lang.Holder;
import cn.jingyuan.owl.utils.core.lang.PatternPool;
import cn.jingyuan.owl.utils.core.lang.Validator;
import cn.jingyuan.owl.utils.core.lang.func.Func1;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 正则相关工具类<br>
 * 常用正则请见 {@link Validator}
 */
public class RegExUtils {

    /** 正则表达式匹配中文汉字 */
    public final static String RE_CHINESE = "[\u4E00-\u9FFF]";

    /** 正则表达式匹配中文字符串 */
    public final static String RE_CHINESES = RE_CHINESE + "+";

    /** 正则中需要被转义的关键字 */
    public final static Set<Character> RE_KEYS = CollectionUtils.newHashSet('$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|');

    /**
     * 获得匹配的字符串，获得正则中分组 0 的内容
     *
     * @param regex 匹配的正则
     * @param content 被匹配的内容
     *
     * @return 匹配后得到的字符串，未匹配返回 null
     */
    public static String getGroup0(String regex, CharSequence content) {
        return get(regex, content, 0);
    }

    /**
     * 获得匹配的字符串，获得正则中分组 1 的内容
     *
     * @param regex 匹配的正则
     * @param content 被匹配的内容
     *
     * @return 匹配后得到的字符串，未匹配返回 null
     */
    public static String getGroup1(String regex, CharSequence content) {
        return get(regex, content, 1);
    }

    /**
     * 获得匹配的字符串
     *
     * @param regex 匹配的正则
     * @param content 被匹配的内容
     * @param groupIndex 匹配正则的分组序号
     *
     * @return 匹配后得到的字符串，未匹配返回 null
     */
    public static String get(String regex, CharSequence content, int groupIndex) {
        if (null == content || null == regex) {
            return null;
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return get(pattern, content, groupIndex);
    }

    /**
     * 获得匹配的字符串，，获得正则中分组 0 的内容
     *
     * @param pattern 编译后的正则模式
     * @param content 被匹配的内容
     *
     * @return 匹配后得到的字符串，未匹配返回 null
     */
    public static String getGroup0(Pattern pattern, CharSequence content) {
        return get(pattern, content, 0);
    }

    /**
     * 获得匹配的字符串，，获得正则中分组 1 的内容
     *
     * @param pattern 编译后的正则模式
     * @param content 被匹配的内容
     *
     * @return 匹配后得到的字符串，未匹配返回 null
     */
    public static String getGroup1(Pattern pattern, CharSequence content) {
        return get(pattern, content, 1);
    }

    /**
     * 获得匹配的字符串，对应分组 0 表示整个匹配内容，1 表示第一个括号分组内容，依次类推
     *
     * @param pattern 编译后的正则模式
     * @param content 被匹配的内容
     * @param groupIndex 匹配正则的分组序号，0 表示整个匹配内容，1 表示第一个括号分组内容，依次类推
     *
     * @return 匹配后得到的字符串，未匹配返回 null
     */
    public static String get(Pattern pattern, CharSequence content, int groupIndex) {
        if (null == content || null == pattern) {
            return null;
        }

        final Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            return matcher.group(groupIndex);
        }
        return null;
    }

    /**
     * 获得匹配的字符串匹配到的所有分组
     *
     * @param pattern 编译后的正则模式
     * @param content 被匹配的内容
     *
     * @return 匹配后得到的字符串数组，按照分组顺序依次列出，未匹配到返回空列表，任何一个参数为 null 返回 null
     */
    public static List<String> getAllGroups(Pattern pattern, CharSequence content) {
        return getAllGroups(pattern, content, true);
    }

    /**
     * 获得匹配的字符串匹配到的所有分组
     *
     * @param pattern 编译后的正则模式
     * @param content 被匹配的内容
     * @param withGroup0 是否包括分组 0，此分组表示全匹配的信息
     *
     * @return 匹配后得到的字符串数组，按照分组顺序依次列出，未匹配到返回空列表，任何一个参数为 null 返回 null
     */
    public static List<String> getAllGroups(Pattern pattern, CharSequence content, boolean withGroup0) {
        if (null == content || null == pattern) {
            return null;
        }

        ArrayList<String> result = new ArrayList<>();
        final Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            final int startGroup = withGroup0 ? 0 : 1;
            final int groupCount = matcher.groupCount();
            for (int i = startGroup; i <= groupCount; i++) {
                result.add(matcher.group(i));
            }
        }
        return result;
    }

    /**
     * 从 content 中匹配出多个值并根据 template 生成新的字符串<br>
     * 例如：<br>
     * content 2013 年5 月 pattern (.*?)年(.*?)月 template： $1-$2 return 2013-5
     *
     * @param pattern 匹配正则
     * @param content 被匹配的内容
     * @param template 生成内容模板，变量 $1 表示 group1 的内容，以此类推
     *
     * @return 新字符串
     */
    public static String extractMulti(Pattern pattern, CharSequence content, String template) {
        if (null == content || null == pattern || null == template) {
            return null;
        }

        // 提取模板中的编号
        final TreeSet<Integer> varNums = new TreeSet<>(new Comparator<Integer>() {

            @Override
            public int compare(Integer o1, Integer o2) {
                return ObjectUtils.compare(o2, o1);
            }
        });
        final Matcher matcherForTemplate = PatternPool.GROUP_VAR.matcher(template);
        while (matcherForTemplate.find()) {
            varNums.add(Integer.parseInt(matcherForTemplate.group(1)));
        }

        final Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            for (Integer group : varNums) {
                template = template.replace("$" + group, matcher.group(group));
            }
            return template;
        }
        return null;
    }

    /**
     * 从 content 中匹配出多个值并根据 template 生成新的字符串<br>
     * 匹配结束后会删除匹配内容之前的内容（包括匹配内容）<br>
     * 例如：<br>
     * content 2013 年5 月 pattern (.*?)年(.*?)月 template： $1-$2 return 2013-5
     *
     * @param regex 匹配正则字符串
     * @param content 被匹配的内容
     * @param template 生成内容模板，变量 $1 表示 group1 的内容，以此类推
     *
     * @return 按照 template 拼接后的字符串
     */
    public static String extractMulti(String regex, CharSequence content, String template) {
        if (null == content || null == regex || null == template) {
            return null;
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return extractMulti(pattern, content, template);
    }

    /**
     * 从 content 中匹配出多个值并根据 template 生成新的字符串<br>
     * 匹配结束后会删除匹配内容之前的内容（包括匹配内容）<br>
     * 例如：<br>
     * content 2013 年5 月 pattern (.*?)年(.*?)月 template： $1-$2 return 2013-5
     *
     * @param pattern 匹配正则
     * @param contentHolder 被匹配的内容的 Holder，value 为内容正文，经过这个方法的原文将被去掉匹配之前的内容
     * @param template 生成内容模板，变量 $1 表示 group1 的内容，以此类推
     *
     * @return 新字符串
     */
    public static String extractMultiAndDelPre(Pattern pattern, Holder<CharSequence> contentHolder, String template) {
        if (null == contentHolder || null == pattern || null == template) {
            return null;
        }

        HashSet<String> varNums = findAll(PatternPool.GROUP_VAR, template, 1, new HashSet<String>());

        final CharSequence content = contentHolder.get();
        Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            for (String var : varNums) {
                int group = Integer.parseInt(var);
                template = template.replace("$" + var, matcher.group(group));
            }
            contentHolder.set(StringUtils.sub(content, matcher.end(), content.length()));
            return template;
        }
        return null;
    }

    /**
     * 从 content 中匹配出多个值并根据 template 生成新的字符串<br>
     * 例如：<br>
     * content 2013 年5 月 pattern (.*?)年(.*?)月 template： $1-$2 return 2013-5
     *
     * @param regex 匹配正则字符串
     * @param contentHolder 被匹配的内容的 Holder，value 为内容正文，经过这个方法的原文将被去掉匹配之前的内容
     * @param template 生成内容模板，变量 $1 表示 group1 的内容，以此类推
     *
     * @return 按照 template 拼接后的字符串
     */
    public static String extractMultiAndDelPre(String regex, Holder<CharSequence> contentHolder, String template) {
        if (null == contentHolder || null == regex || null == template) {
            return null;
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return extractMultiAndDelPre(pattern, contentHolder, template);
    }

    /**
     * 删除匹配的第一个内容
     *
     * @param regex 正则
     * @param content 被匹配的内容
     *
     * @return 删除后剩余的内容
     */
    public static String delFirst(String regex, CharSequence content) {
        if (StringUtils.hasBlank(regex, content)) {
            return StringUtils.string(content);
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return delFirst(pattern, content);
    }

    /**
     * 删除匹配的第一个内容
     *
     * @param pattern 正则
     * @param content 被匹配的内容
     *
     * @return 删除后剩余的内容
     */
    public static String delFirst(Pattern pattern, CharSequence content) {
        if (null == pattern || StringUtils.isBlank(content)) {
            return StringUtils.string(content);
        }

        return pattern.matcher(content).replaceFirst(StringUtils.EMPTY);
    }

    /**
     * 删除匹配的全部内容
     *
     * @param regex 正则
     * @param content 被匹配的内容
     *
     * @return 删除后剩余的内容
     */
    public static String delAll(String regex, CharSequence content) {
        if (StringUtils.hasBlank(regex, content)) {
            return StringUtils.string(content);
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return delAll(pattern, content);
    }

    /**
     * 删除匹配的全部内容
     *
     * @param pattern 正则
     * @param content 被匹配的内容
     *
     * @return 删除后剩余的内容
     */
    public static String delAll(Pattern pattern, CharSequence content) {
        if (null == pattern || StringUtils.isBlank(content)) {
            return StringUtils.string(content);
        }

        return pattern.matcher(content).replaceAll(StringUtils.EMPTY);
    }

    /**
     * 删除正则匹配到的内容之前的字符 如果没有找到，则返回原文
     *
     * @param regex 定位正则
     * @param content 被查找的内容
     *
     * @return 删除前缀后的新内容
     */
    public static String delPre(String regex, CharSequence content) {
        if (null == content || null == regex) {
            return StringUtils.string(content);
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            return StringUtils.sub(content, matcher.end(), content.length());
        }
        return StringUtils.string(content);
    }

    /**
     * 取得内容中匹配的所有结果，获得匹配的所有结果中正则对应分组 0 的内容
     *
     * @param regex 正则
     * @param content 被查找的内容
     *
     * @return 结果列表
     */
    public static List<String> findAllGroup0(String regex, CharSequence content) {
        return findAll(regex, content, 0);
    }

    /**
     * 取得内容中匹配的所有结果，获得匹配的所有结果中正则对应分组 1 的内容
     *
     * @param regex 正则
     * @param content 被查找的内容
     *
     * @return 结果列表
     */
    public static List<String> findAllGroup1(String regex, CharSequence content) {
        return findAll(regex, content, 1);
    }

    /**
     * 取得内容中匹配的所有结果
     *
     * @param regex 正则
     * @param content 被查找的内容
     * @param group 正则的分组
     *
     * @return 结果列表
     */
    public static List<String> findAll(String regex, CharSequence content, int group) {
        return findAll(regex, content, group, new ArrayList<String>());
    }

    /**
     * 取得内容中匹配的所有结果
     *
     * @param <T> 集合类型
     * @param regex 正则
     * @param content 被查找的内容
     * @param group 正则的分组
     * @param collection 返回的集合类型
     *
     * @return 结果集
     */
    public static <T extends Collection<String>> T findAll(String regex, CharSequence content, int group, T collection) {
        if (null == regex) {
            return collection;
        }

        return findAll(Pattern.compile(regex, Pattern.DOTALL), content, group, collection);
    }

    /**
     * 取得内容中匹配的所有结果，获得匹配的所有结果中正则对应分组 0 的内容
     *
     * @param pattern 编译后的正则模式
     * @param content 被查找的内容
     *
     * @return 结果列表
     */
    public static List<String> findAllGroup0(Pattern pattern, CharSequence content) {
        return findAll(pattern, content, 0);
    }

    /**
     * 取得内容中匹配的所有结果，获得匹配的所有结果中正则对应分组 1 的内容
     *
     * @param pattern 编译后的正则模式
     * @param content 被查找的内容
     *
     * @return 结果列表
     */
    public static List<String> findAllGroup1(Pattern pattern, CharSequence content) {
        return findAll(pattern, content, 1);
    }

    /**
     * 取得内容中匹配的所有结果
     *
     * @param pattern 编译后的正则模式
     * @param content 被查找的内容
     * @param group 正则的分组
     *
     * @return 结果列表
     */
    public static List<String> findAll(Pattern pattern, CharSequence content, int group) {
        return findAll(pattern, content, group, new ArrayList<String>());
    }

    /**
     * 取得内容中匹配的所有结果
     *
     * @param <T> 集合类型
     * @param pattern 编译后的正则模式
     * @param content 被查找的内容
     * @param group 正则的分组
     * @param collection 返回的集合类型
     *
     * @return 结果集
     */
    public static <T extends Collection<String>> T findAll(Pattern pattern, CharSequence content, int group, T collection) {
        if (null == pattern || null == content) {
            return null;
        }

        if (null == collection) {
            throw new NullPointerException("Null collection param provided!");
        }

        final Matcher matcher = pattern.matcher(content);
        while (matcher.find()) {
            collection.add(matcher.group(group));
        }
        return collection;
    }

    /**
     * 计算指定字符串中，匹配 pattern 的个数
     *
     * @param regex 正则表达式
     * @param content 被查找的内容
     *
     * @return 匹配个数
     */
    public static int count(String regex, CharSequence content) {
        if (null == regex || null == content) {
            return 0;
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return count(pattern, content);
    }

    /**
     * 计算指定字符串中，匹配 pattern 的个数
     *
     * @param pattern 编译后的正则模式
     * @param content 被查找的内容
     *
     * @return 匹配个数
     */
    public static int count(Pattern pattern, CharSequence content) {
        if (null == pattern || null == content) {
            return 0;
        }

        int count = 0;
        final Matcher matcher = pattern.matcher(content);
        while (matcher.find()) {
            count++;
        }

        return count;
    }

    /**
     * 指定内容中是否有表达式匹配的内容
     *
     * @param regex 正则表达式
     * @param content 被查找的内容
     *
     * @return 指定内容中是否有表达式匹配的内容
     */
    public static boolean contains(String regex, CharSequence content) {
        if (null == regex || null == content) {
            return false;
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return contains(pattern, content);
    }

    /**
     * 指定内容中是否有表达式匹配的内容
     *
     * @param pattern 编译后的正则模式
     * @param content 被查找的内容
     *
     * @return 指定内容中是否有表达式匹配的内容
     */
    public static boolean contains(Pattern pattern, CharSequence content) {
        if (null == pattern || null == content) {
            return false;
        }
        return pattern.matcher(content).find();
    }

    /**
     * 从字符串中获得第一个整数
     *
     * @param stringWithNumber 带数字的字符串
     *
     * @return 整数
     */
    public static Integer getFirstNumber(CharSequence stringWithNumber) {
        return Convert.toInt(get(PatternPool.NUMBERS, stringWithNumber, 0), null);
    }

    /**
     * 给定内容是否匹配正则
     *
     * @param regex 正则
     * @param content 内容
     *
     * @return 正则为 null 或者""则不检查，返回 true，内容为 null 返回 false
     */
    public static boolean isMatch(String regex, CharSequence content) {
        if (content == null) {
            // 提供 null 的字符串为不匹配
            return false;
        }

        if (StringUtils.isEmpty(regex)) {
            // 正则不存在则为全匹配
            return true;
        }

        final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
        return isMatch(pattern, content);
    }

    /**
     * 给定内容是否匹配正则
     *
     * @param pattern 模式
     * @param content 内容
     *
     * @return 正则为 null 或者""则不检查，返回 true，内容为 null 返回 false
     */
    public static boolean isMatch(Pattern pattern, CharSequence content) {
        if (content == null || pattern == null) {
            // 提供 null 的字符串为不匹配
            return false;
        }
        return pattern.matcher(content).matches();
    }

    /**
     * 正则替换指定值<br>
     * 通过正则查找到字符串，然后把匹配到的字符串加入到 replacementTemplate 中，$1 表示分组 1 的字符串
     *
     * <p>
     * 例如：原字符串是：中文 1234，我想把 1234 换成(1234)，则可以：
     *
     * <pre>
     * ReUtil.replaceAll("中文1234", "(\\d+)", "($1)"))
     *
     * 结果：中文(1234)
     * </pre>
     *
     * @param content 文本
     * @param regex 正则
     * @param replacementTemplate 替换的文本模板，可以使用$1 类似的变量提取正则匹配出的内容
     *
     * @return 处理后的文本
     */
    public static String replaceAll(CharSequence content, String regex, String replacementTemplate) {
        final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        return replaceAll(content, pattern, replacementTemplate);
    }

    /**
     * 正则替换指定值<br>
     * 通过正则查找到字符串，然后把匹配到的字符串加入到 replacementTemplate 中，$1 表示分组 1 的字符串
     *
     * @param content 文本
     * @param pattern {@link Pattern}
     * @param replacementTemplate 替换的文本模板，可以使用$1 类似的变量提取正则匹配出的内容
     *
     * @return 处理后的文本
     */
    public static String replaceAll(CharSequence content, Pattern pattern, String replacementTemplate) {
        if (StringUtils.isEmpty(content)) {
            return StringUtils.string(content);
        }

        final Matcher matcher = pattern.matcher(content);
        boolean result = matcher.find();
        if (result) {
            final Set<String> varNums = findAll(PatternPool.GROUP_VAR, replacementTemplate, 1, new HashSet<String>());
            final StringBuffer sb = new StringBuffer();
            do {
                String replacement = replacementTemplate;
                for (String var : varNums) {
                    int group = Integer.parseInt(var);
                    replacement = replacement.replace("$" + var, matcher.group(group));
                }
                matcher.appendReplacement(sb, escape(replacement));
                result = matcher.find();
            } while (result);
            matcher.appendTail(sb);
            return sb.toString();
        }
        return StringUtils.string(content);
    }

    /**
     * 替换所有正则匹配的文本，并使用自定义函数决定如何替换
     *
     * @param str 要替换的字符串
     * @param regex 用于匹配的正则式
     * @param replaceFun 决定如何替换的函数
     *
     * @return 替换后的文本
     */
    public static String replaceAll(CharSequence str, String regex, Func1<Matcher, String> replaceFun) {
        return replaceAll(str, Pattern.compile(regex), replaceFun);
    }

    /**
     * 替换所有正则匹配的文本，并使用自定义函数决定如何替换
     *
     * @param str 要替换的字符串
     * @param pattern 用于匹配的正则式
     * @param replaceFun 决定如何替换的函数,可能被多次调用（当有多个匹配时）
     *
     * @return 替换后的字符串
     */
    public static String replaceAll(CharSequence str, Pattern pattern, Func1<Matcher, String> replaceFun) {
        if (StringUtils.isEmpty(str)) {
            return StringUtils.string(str);
        }

        final Matcher matcher = pattern.matcher(str);
        final StringBuffer buffer = new StringBuffer();
        while (matcher.find()) {
            try {
                matcher.appendReplacement(buffer, replaceFun.call(matcher));
            } catch (Exception e) {
                throw new UtilsException(e);
            }
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }

    /**
     * 转义字符，将正则的关键字转义
     *
     * @param c 字符
     *
     * @return 转义后的文本
     */
    public static String escape(char c) {
        final StringBuilder builder = new StringBuilder();
        if (RE_KEYS.contains(c)) {
            builder.append('\\');
        }
        builder.append(c);
        return builder.toString();
    }

    /**
     * 转义字符串，将正则的关键字转义
     *
     * @param content 文本
     *
     * @return 转义后的文本
     */
    public static String escape(CharSequence content) {
        if (StringUtils.isBlank(content)) {
            return StringUtils.string(content);
        }

        final StringBuilder builder = new StringBuilder();
        int len = content.length();
        char current;
        for (int i = 0; i < len; i++) {
            current = content.charAt(i);
            if (RE_KEYS.contains(current)) {
                builder.append('\\');
            }
            builder.append(current);
        }
        return builder.toString();
    }

}